/* Digital Mars DMDScript source code.
 * Copyright (c) 2000-2002 by Chromium Communications
 * D version Copyright (c) 2004-2010 by Digital Mars
 * Distributed under the Boost Software License, Version 1.0.
 * (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
 * written by Walter Bright
 * http://www.digitalmars.com
 *
 * D2 port by Dmitry Olshansky 
 *
 * DMDScript is implemented in the D Programming Language,
 * http://www.digitalmars.com/d/
 *
 * For a C++ implementation of DMDScript, including COM support, see
 * http://www.digitalmars.com/dscript/cppscript.html
 */


module dmdscript.dregexp;

// private import dmdscript.regexp;

import dmdscript.script;
import dmdscript.dobject;
import dmdscript.value;
import dmdscript.protoerror;
import dmdscript.text;
import dmdscript.darray;
import dmdscript.threadcontext;
import dmdscript.dfunction;
import dmdscript.property;
import dmdscript.errmsgs;
import dmdscript.dnative;

import dmdscript.pcre;

//alias script.tchar tchar;

// Values for Dregexp.exec.rettype
enum { EXEC_STRING, EXEC_ARRAY, EXEC_BOOLEAN, EXEC_INDEX };


/* ===================== Dregexp_constructor ==================== */

class DregexpConstructor : Dfunction
{
    Value* input;
    Value* multiline;
    Value* lastMatch;
    Value* lastParen;
    Value* leftContext;
    Value* rightContext;
    Value* dollar[10];

    // Extensions
    Value* index;
    Value* lastIndex;

    this()
    {
        super(2, Dfunction_prototype);

        Value v;
        v.putVstring(null);

        Value vb;
        vb.putVboolean(false);

        Value vnm1;
        vnm1.putVnumber(-1);

        name = "RegExp";

        // Static properties
        Put(TEXT_input, &v, DontDelete);
        Put(TEXT_multiline, &vb, DontDelete);
        Put(TEXT_lastMatch, &v, ReadOnly | DontDelete);
        Put(TEXT_lastParen, &v, ReadOnly | DontDelete);
        Put(TEXT_leftContext, &v, ReadOnly | DontDelete);
        Put(TEXT_rightContext, &v, ReadOnly | DontDelete);
        Put(TEXT_dollar1, &v, ReadOnly | DontDelete);
        Put(TEXT_dollar2, &v, ReadOnly | DontDelete);
        Put(TEXT_dollar3, &v, ReadOnly | DontDelete);
        Put(TEXT_dollar4, &v, ReadOnly | DontDelete);
        Put(TEXT_dollar5, &v, ReadOnly | DontDelete);
        Put(TEXT_dollar6, &v, ReadOnly | DontDelete);
        Put(TEXT_dollar7, &v, ReadOnly | DontDelete);
        Put(TEXT_dollar8, &v, ReadOnly | DontDelete);
        Put(TEXT_dollar9, &v, ReadOnly | DontDelete);

        Put(TEXT_index, &vnm1, ReadOnly | DontDelete);
        Put(TEXT_lastIndex, &vnm1, ReadOnly | DontDelete);

        input = Get(TEXT_input);
        multiline = Get(TEXT_multiline);
        lastMatch = Get(TEXT_lastMatch);
        lastParen = Get(TEXT_lastParen);
        leftContext = Get(TEXT_leftContext);
        rightContext = Get(TEXT_rightContext);
        dollar[0] = lastMatch;
        dollar[1] = Get(TEXT_dollar1);
        dollar[2] = Get(TEXT_dollar2);
        dollar[3] = Get(TEXT_dollar3);
        dollar[4] = Get(TEXT_dollar4);
        dollar[5] = Get(TEXT_dollar5);
        dollar[6] = Get(TEXT_dollar6);
        dollar[7] = Get(TEXT_dollar7);
        dollar[8] = Get(TEXT_dollar8);
        dollar[9] = Get(TEXT_dollar9);

        index = Get(TEXT_index);
        lastIndex = Get(TEXT_lastIndex);

        // Should lastMatch be an alias for dollar[nparens],
        // or should it be a separate property?
        // We implemented it the latter way.
        // Since both are ReadOnly, I can't see that it makes
        // any difference.
    }

    override void* Construct(CallContext *cc, Value *ret, Value[] arglist)
    {
        // ECMA 262 v3 15.10.4.1

        Value* pattern;
        Value* flags;
        d_string P;
        d_string F;
        Dregexp r;
        Dregexp R;

        //writef("Dregexp_constructor.Construct()\n");
        ret.putVundefined();
        pattern = &vundefined;
        flags = &vundefined;
        switch(arglist.length)
        {
        case 0:
            break;

        default:
            flags = &arglist[1];
        case 1:
            pattern = &arglist[0];
            break;
        }
        R = Dregexp.isRegExp(pattern);
        if(R)
        {
            if(flags.isUndefined())
            {
                P = R.pattern;
                F = R.flags;
            }
            else
            {
                ErrInfo errinfo;
                return RuntimeError(&errinfo, ERR_TYPE_ERROR,
                                    "RegExp.prototype.constructor");
            }
        }
        else
        {
            P = pattern.isUndefined() ? "" : pattern.toString();
            F = flags.isUndefined() ? "" : flags.toString();
        }
        r = new Dregexp(P, F);
        if(r.re.error)
        {
            Dobject o;
            ErrInfo errinfo;

            version(none)
            {
                writef("P = '%s'\nF = '%s'\n", d_string_ptr(P), d_string_ptr(F));
                for(int i = 0; i < d_string_len(P); i++)
                    writef("x%02x\n", d_string_ptr(P)[i]);
            }
            errinfo.message = errmsgtbl[ERR_REGEXP_COMPILE];
            o = new syntaxerror.D0(&errinfo);
            Value* v = new Value;
            v.putVobject(o);
            return v;
        }
        else
        {
            ret.putVobject(r);
            return null;
        }
    }

    override void* Call(CallContext *cc, Dobject othis, Value* ret, Value[] arglist)
    {
        // ECMA 262 v3 15.10.3.1
        if(arglist.length >= 1)
        {
            Value* pattern;
            Dobject o;

            pattern = &arglist[0];
            if(!pattern.isPrimitive())
            {
                o = pattern.object;
                if(o.isDregexp() &&
                   (arglist.length == 1 || arglist[1].isUndefined())
                   )
                {
                    ret.putVobject(o);
                    return null;
                }
            }
        }
        return Construct(cc, ret, arglist);
    }


    Value* Get(d_string PropertyName) const
    {
        return Dfunction.Get(perlAlias(PropertyName));
    }

    Value* Put(d_string PropertyName, Value* value, uint attributes)
    {
        return Dfunction.Put(perlAlias(PropertyName), value, attributes);
    }

    Value* Put(d_string PropertyName, Dobject o, uint attributes)
    {
        return Dfunction.Put(perlAlias(PropertyName), o, attributes);
    }

    Value* Put(d_string PropertyName, d_number n, uint attributes)
    {
        return Dfunction.Put(perlAlias(PropertyName), n, attributes);
    }

    int CanPut(d_string PropertyName)
    {
        return Dfunction.CanPut(perlAlias(PropertyName));
    }

    int HasProperty(d_string PropertyName)
    {
        return Dfunction.HasProperty(perlAlias(PropertyName));
    }

    int Delete(d_string PropertyName)
    {
        return Dfunction.Delete(perlAlias(PropertyName));
    }

    // Translate Perl property names to script property names
    static d_string perlAlias(d_string s)
    {
        d_string t;

        static tchar[6] from = "_*&+`'";
        static enum d_string[] to =
        [
            TEXT_input,
            TEXT_multiline,
            TEXT_lastMatch,
            TEXT_lastParen,
            TEXT_leftContext,
            TEXT_rightContext,
        ];

        t = s;
        if(s.length == 2 && s[0] == '$')
        {
            int i;

            i = std.string.indexOf(from[], s[1]);
            if(i >= 0)
                t = to[i];
        }
        return t;
    }
}


/* ===================== Dregexp_prototype_toString =============== */

void* Dregexp_prototype_toString(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
{
    // othis must be a RegExp
    Dregexp r;

    if(!othis.isDregexp())
    {
        ret.putVundefined();
        ErrInfo errinfo;
        return Dobject.RuntimeError(&errinfo, ERR_NOT_TRANSFERRABLE,
                                    "RegExp.prototype.toString()");
    }
    else
    {
        d_string s;

        r = cast(Dregexp)(othis);
        s = "/";
        s ~= r.pattern;
        s ~= "/";
        s ~= r.flags;
        ret.putVstring(s);
    }
    return null;
}

/* ===================== Dregexp_prototype_test =============== */

void* Dregexp_prototype_test(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
{
    // ECMA v3 15.10.6.3 says this is equivalent to:
    //	RegExp.prototype.exec(string) != null
    return Dregexp.exec(othis, ret, arglist, EXEC_BOOLEAN);
}

/* ===================== Dregexp_prototype_exec ============= */

void* Dregexp_prototype_exec(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
{
    return Dregexp.exec(othis, ret, arglist, EXEC_ARRAY);
}


/* ===================== Dregexp_prototype_compile ============= */

void* Dregexp_prototype_compile(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist)
{
    // RegExp.prototype.compile(pattern, attributes)

    // othis must be a RegExp
    if(!othis.isClass(TEXT_RegExp))
    {
        ErrInfo errinfo;
        ret.putVundefined();
        return Dobject.RuntimeError(&errinfo, ERR_NOT_TRANSFERRABLE,
                                    "RegExp.prototype.compile()");
    }
    else
    {
        d_string pattern;
        d_string attributes;
        Dregexp dr;
        RegExp r;

        dr = cast(Dregexp)othis;
        switch(arglist.length)
        {
        case 0:
            break;

        default:
            attributes = arglist[1].toString();
        case 1:
            pattern = arglist[0].toString();
            break;
        }

        r = dr.re;
	
	if( !r.compile(pattern, 0) ) { //attributes
            // Affect source, global and ignoreCase properties
            dr.source.putVstring(dr.pattern);
            dr.global.putVboolean( dr._global ) ;
            dr.ignoreCase.putVboolean((r.attributes & RegExp.REA.ignoreCase) != 0);
        }
        //writef("r.attributes = x%x\n", r.attributes);
    }
    // Documentation says nothing about a return value,
    // so let's use "undefined"
    ret.putVundefined();
    return null;
}

/* ===================== Dregexp_prototype ==================== */

class DregexpPrototype : Dregexp
{
    this()
    {
        super(Dobject_prototype);
        classname = TEXT_Object;
        uint attributes = ReadOnly | DontDelete | DontEnum;
        Dobject f = Dfunction_prototype;

        Put(TEXT_constructor, Dregexp_constructor, attributes);

        static enum NativeFunctionData nfd[] =
        [
            { TEXT_toString, &Dregexp_prototype_toString, 0 },
            { TEXT_compile, &Dregexp_prototype_compile, 2 },
            { TEXT_exec, &Dregexp_prototype_exec, 1 },
            { TEXT_test, &Dregexp_prototype_test, 1 },
        ];

        DnativeFunction.init(this, nfd, attributes);
    }
}


/* ===================== Dregexp ==================== */


class Dregexp : Dobject
{
    Value *global;
    Value *ignoreCase;
    Value *multiline;
    Value *lastIndex;
    Value *source;

	RegExp re;
	string pattern, _options ;
	PCRE_INT options ;
	bool	_global ;
	string flags(){
		return _options;
	}
	
	void flags(string _flags) {
		_options = _flags ;

		PCRE_INT options = 0 ;
		_global	= false ;
		
		if(_options !is null) foreach( c; _options){
			switch(c){
				case 'm':
					options	|=	PCRE_MULTILINE ;
					break;
				case 'i':
					options	|=	PCRE_CASELESS ;
					break;
				case 's':
					options	|=	PCRE_DOTALL ;
					break;
				case 'x':
					options	|=	PCRE_EXTENDED ;
					break;
				case 'u':
					options	|=	PCRE_UTF8 ;
					break;
				case 'g':
					_global	= true ;
					break;
				default:
					break;
			}
		}
		this.options	= options;
	}

	void *Call(CallContext *cc, Dobject othis, Value* ret, Value[] arglist);

	this(d_string pattern, d_string attributes)
	{
		super(getPrototype());

		Value v;
		v.putVstring(null);

		Value vb;
		vb.putVboolean(false);

		classname = TEXT_RegExp;

		//writef("Dregexp.Dregexp(pattern = '%ls', attributes = '%ls')\n", d_string_ptr(pattern), d_string_ptr(attributes));
		Put(TEXT_source, &v, ReadOnly | DontDelete | DontEnum);
		Put(TEXT_global, &vb, ReadOnly | DontDelete | DontEnum);
		Put(TEXT_ignoreCase, &vb, ReadOnly | DontDelete | DontEnum);
		Put(TEXT_multiline, &vb, ReadOnly | DontDelete | DontEnum);
		Put(TEXT_lastIndex, 0.0, DontDelete | DontEnum);

		source = Get(TEXT_source);
		global = Get(TEXT_global);
		ignoreCase = Get(TEXT_ignoreCase);
		multiline = Get(TEXT_multiline);
		lastIndex = Get(TEXT_lastIndex);
		this.pattern	= pattern;
		this.flags		= attributes ;

		if( re.compile(pattern, options) ) {
			source.putVstring(pattern);

			// log(re.attributes, ", options=" , options, " RegExp.REA.multiline=", RegExp.REA.multiline);

			global.putVboolean( _global );
			ignoreCase.putVboolean((re.attributes & RegExp.REA.ignoreCase) != 0);
			multiline.putVboolean((re.attributes & RegExp.REA.multiline) != 0);
		} else {
			// have caller throw SyntaxError
			assert(false, re.error);
		}
	}
    
    

	this(Dobject prototype) {
		super(prototype);

		Value v;
		v.putVstring(null);

		Value vb;
		vb.putVboolean(false);

		classname = TEXT_RegExp;

		Put(TEXT_source, &v, ReadOnly | DontDelete | DontEnum);
		Put(TEXT_global, &vb, ReadOnly | DontDelete | DontEnum);
		Put(TEXT_ignoreCase, &vb, ReadOnly | DontDelete | DontEnum);
		Put(TEXT_multiline, &vb, ReadOnly | DontDelete | DontEnum);
		Put(TEXT_lastIndex, 0.0, DontDelete | DontEnum);

		source = Get(TEXT_source);
		global = Get(TEXT_global);
		ignoreCase = Get(TEXT_ignoreCase);
		multiline = Get(TEXT_multiline);
		lastIndex = Get(TEXT_lastIndex);
	}

	override void* Call(CallContext *cc, Dobject othis, Value* ret, Value[] arglist) {
		// This is the same as calling RegExp.prototype.exec(str)
		Value* v;
		v = Get(TEXT_exec);
		return v.toObject().Call(cc, this, ret, arglist);
	}

	static Dregexp isRegExp(Value* v){
		Dregexp r;
		if(!v.isPrimitive() && v.toObject().isDregexp())
		{
		 	 r = cast(Dregexp)(v.toObject());
		}
		return r;
	}
	alias void delegate(string[]) exec_dg;
	static void* exec(Dobject othis, Value* ret, Value[] arglist, int rettype, exec_dg dg =null )
	{
		// othis must be a RegExp
		if(!othis.isClass(TEXT_RegExp)) {
			ret.putVundefined();
			ErrInfo errinfo;
			return RuntimeError(&errinfo, ERR_NOT_TRANSFERRABLE,
					"RegExp.prototype.exec()");
		} else {
			d_string s;
			Dregexp dr;
			DregexpConstructor dc;
			uint i, lasti;

			if(arglist.length){
				s = arglist[0].toString();
			} else {
				Dfunction df;

				df = Dregexp.getConstructor();
				s = (cast(DregexpConstructor)df).input.string;
			}

			dr = cast(Dregexp)othis;
			dc = cast(DregexpConstructor)Dregexp.getConstructor();

			if( dr._global && rettype != EXEC_INDEX) {
				lasti = cast(int)dr.lastIndex.toInteger();
				// log(`lasti =`, lasti);
			} else {
				lasti = 0;
			}
			
			if(
				!dr.re.each(
					s, 
					(string[] m) {
						scope(exit){
							if(dg !is null) {
								dg(m) ;
							}
						}
						
               					dc.index.putVnumber( &m[0] [0] - s.ptr  );
						if( dr._global && rettype != EXEC_INDEX)
						{
							int _lastIndex	= &m[0] [$-1] - s.ptr +1;
							dc.lastIndex.putVnumber( _lastIndex ) ;
							// log(`lasti =`,  _lastIndex);
						} else {
							dc.lastIndex.putVnumber( -1 ) ;
						}
						dc.input.putVstring(s);
						assert(m[0].length > 0);
						dc.lastMatch.putVstring(m[0]);
						dc.leftContext.putVstring(s[0 .. m[0].ptr - s.ptr ] );
						dc.rightContext.putVstring(s[  &m[0][$-1] - s.ptr + 1.. $ ]);
						if(  m.length > 1 ) {
							dc.lastParen.putVstring( m[$-1] ) ;
						} else {
							dc.lastParen.putVundefined();
						}
						
						// Fill in $1..$9
						for(i = 1; i <= 9; i++) {
							if(i < m.length)
							{
								// log(i, "=", "`", m[i], "`");
								if( m[i].length is 0 ) {
									dc.dollar[i].putVundefined();
								} else {
									dc.dollar[i].putVstring( m[i] );
									//lastv = dc.dollar[i];
								}
							} else {
								dc.dollar[i].putVundefined();
							}
						}

						switch(rettype)
						{
							case EXEC_ARRAY:
							{
								Darray a = new Darray();
								
								a.Put(TEXT_input, s, 0);
								a.Put(TEXT_index,  &m[0] [0] - s.ptr , 0);
								a.Put(TEXT_lastIndex, &m[0] [$-1] - s.ptr + 1, 0);

								a.Put(cast(d_uint32)0, dc.lastMatch, cast(uint)0);

								// [1]..[nparens]
								foreach(int i, _m; m) if(i) {
									if( i > 9 ) {
										a.Put(i, TEXT_, 0);
									} else {
										a.Put(i, dc.dollar[i], 0);
									}
								}
								
								ret.putVobject(a);
								break;
							}
							case EXEC_STRING:
								Value.copy(ret, dc.lastMatch);
								break;

							case EXEC_BOOLEAN:
								ret.putVboolean(true);      // success
								break;

							case EXEC_INDEX:
								ret.putVnumber( m[0].length);
								break;

							default:
								assert(0);
						}
						return false ;
					} ,
					lasti
				) 
			) {
				// log( " failed s=`", s, "`");
				switch(rettype)
				{
					case EXEC_ARRAY:
					    //writef("memcpy\n");
					    ret.putVnull();         // Return null
					    dr.lastIndex.putVnumber(0);
					    break;

					case EXEC_STRING:
					    ret.putVstring(null);
					    dr.lastIndex.putVnumber(0);
					    break;

					case EXEC_BOOLEAN:
					    ret.putVboolean(false);
					    dr.lastIndex.putVnumber(0);
					    break;

					case EXEC_INDEX:
					    ret.putVnumber(-1.0);
					    // Do not set lastIndex
					    break;

					default:
					    assert(0);
				}
			} else {
				if( dr._global ) {
					auto _lastIndex	= dc.lastIndex.toInteger ;
					dr.lastIndex.putVnumber( _lastIndex > 0 ? _lastIndex : 0 ) ;
				} else {
					dr.lastIndex.putVnumber( 0 ) ;
				}
			}
		}
		
		return null;
	}

	static Dfunction getConstructor()
	{
		return Dregexp_constructor;
	}

	static Dobject getPrototype()
	{
		return Dregexp_prototype;
	}

	static void init()
	{
		Dregexp_constructor = new DregexpConstructor();
		Dregexp_prototype = new DregexpPrototype();
		Dregexp_constructor.Put(TEXT_prototype, Dregexp_prototype, DontEnum | DontDelete | ReadOnly);
	}
}
